<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="yxeo2" id="yxeo2"><span data-lake-id="u61d352b7" id="u61d352b7">典型回答</span></h1>
  <p data-lake-id="u2270aea1" id="u2270aea1"><br></p>
  <p data-lake-id="u7f55eb49" id="u7f55eb49"><span data-lake-id="u37154971" id="u37154971">在计算机内存中，每个对象都有一个地址，这个地址指向对象在内存中存储的位置。当我们使用变量引用一个对象时，实际上是将该对象的地址赋值给变量。因此，如果我们将一个对象复制到另一个变量中，实际上是将对象的地址复制到了这个变量中。</span></p>
  <p data-lake-id="uef45cd9b" id="uef45cd9b"><span data-lake-id="ubd840ed0" id="ubd840ed0">​</span><br></p>
  <p data-lake-id="u27a5a5cb" id="u27a5a5cb"><strong><span data-lake-id="u82df86c3" id="u82df86c3">浅拷贝是指将一个对象复制到另一个变量中，但是只复制对象的地址，而不是对象本身。也就是说，原始对象和复制对象实际上是共享同一个内存地址的。</span></strong><span data-lake-id="uab680a01" id="uab680a01">因此，如果我们修改了复制对象中的属性或元素，原始对象中对应的属性或元素也会被修改。</span></p>
  <p data-lake-id="u371c31d3" id="u371c31d3"><span data-lake-id="u23b35cdb" id="u23b35cdb">​</span><br></p>
  <p data-lake-id="u47d6f84d" id="u47d6f84d"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1676783507992-17834cb9-e2a9-4e59-aba7-5153a32a5ef5.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u087c5ef5" id="u087c5ef5"><br></p>
  <p data-lake-id="u36f03850" id="u36f03850"><span data-lake-id="uccf5169e" id="uccf5169e">在Java中，我们常用的各种BeanUtils基本也都是浅拷贝的。</span></p>
  <p data-lake-id="u9d11bdb9" id="u9d11bdb9"><br></p>
  <p data-lake-id="u8ae43c00" id="u8ae43c00"><strong><span data-lake-id="ue710b246" id="ue710b246">深拷贝是指将一个对象及其所有子对象都复制到另一个变量中，也就是说，它会创建一个全新的对象，并将原始对象中的所有属性或元素都复制到新的对象中。</span></strong><span data-lake-id="ue79cb129" id="ue79cb129">因此，如果我们修改复制对象中的属性或元素，原始对象中对应的属性或元素不会受到影响。</span></p>
  <p data-lake-id="u5c7c8562" id="u5c7c8562"><span data-lake-id="u68c678b8" id="u68c678b8">​</span><br></p>
  <p data-lake-id="ub75067d6" id="ub75067d6"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1676783597206-2d0338bb-15c2-438e-8692-f17d01aa9fb6.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_28%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u3c0b2711" id="u3c0b2711"><br></p>
  <p data-lake-id="u03b249bf" id="u03b249bf"><span data-lake-id="ubbafe619" id="ubbafe619">比如我们有一个User类，然后他的定义如下：</span></p>
  <p data-lake-id="u3f7bd2a4" id="u3f7bd2a4"><span data-lake-id="uf9804a11" id="uf9804a11">​</span><br></p>
  <pre lang="java"><code>
class User {
    private String name;
    private String password;
    private Address address;
    //省略构造函数和setter/getter
}

public class Address {
    private String province;
    private String city;
    private String area;
    //省略构造函数和setter/getter
}


</code></pre>
  <p data-lake-id="uc73d25dd" id="uc73d25dd"><br></p>
  <p data-lake-id="u68b21893" id="u68b21893"><span data-lake-id="u2dceafe5" id="u2dceafe5">当我们基于User类的一个对象user1拷贝出一个新的对象user2的时候，不管怎么样，user1和user2都是两个不同的对象，他们的地址也不会一样。但是其中的成员变量Address的话可能就因为深浅拷贝的不同而呈现不同的现象了。</span></p>
  <p data-lake-id="u40b9fda4" id="u40b9fda4"><span data-lake-id="udddb8755" id="udddb8755">​</span><br></p>
  <p data-lake-id="u95c4f745" id="u95c4f745"><span data-lake-id="u2278d1c0" id="u2278d1c0">如果是浅拷贝，那么user2中的address会和user1中的address共享同一个地址，当其中一个修改时，另一个也会受影响。</span></p>
  <p data-lake-id="ua47d5361" id="ua47d5361"><span data-lake-id="u0091ae9c" id="u0091ae9c">​</span><br></p>
  <p data-lake-id="ube8fb911" id="ube8fb911"><span data-lake-id="u5d26f45b" id="u5d26f45b">如果是深拷贝，那么user2中的address会和user1中的address并不是同一个地址，当其中一个修改时，另一个是不会受影响的。</span></p>
  <h1 data-lake-id="iynef" id="iynef"><span data-lake-id="u878bd853" id="u878bd853">扩展知识</span></h1>
  <p data-lake-id="ud262f8cb" id="ud262f8cb"><br></p>
  <h2 data-lake-id="Ap5QO" id="Ap5QO"><span data-lake-id="u9c3ee990" id="u9c3ee990">BeanUtils的浅拷贝</span></h2>
  <p data-lake-id="u199f2fc1" id="u199f2fc1"><br></p>
  <p data-lake-id="u9114abf9" id="u9114abf9"><span data-lake-id="u3025014e" id="u3025014e">我们举个实际例子，来看下为啥前面说BeanUtils.copyProperties的过程是浅拷贝。</span></p>
  <p data-lake-id="ud949c0dd" id="ud949c0dd"><span data-lake-id="uf937b3a7" id="uf937b3a7">​</span><br></p>
  <p data-lake-id="u62a4ecfd" id="u62a4ecfd"><span data-lake-id="u590ba7eb" id="u590ba7eb">先来定义两个类：</span></p>
  <p data-lake-id="u40d6d211" id="u40d6d211"><span data-lake-id="u7ca16de3" id="u7ca16de3">​</span><br></p>
  <pre lang="java"><code>
public class Address {
    private String province;
    private String city;
    private String area;
    //省略构造函数和setter/getter
}

class User {
    private String name;
    private String password;
    private Address address;
    //省略构造函数和setter/getter
}
</code></pre>
  <p data-lake-id="ud3055a1c" id="ud3055a1c"><span data-lake-id="udefc94e2" id="udefc94e2">​</span><br></p>
  <p data-lake-id="u26a7592a" id="u26a7592a"><span data-lake-id="u0adcecea" id="u0adcecea">然后写一段测试代码：</span></p>
  <p data-lake-id="uc7b3d415" id="uc7b3d415"><br></p>
  <pre lang="java"><code>
User user = new User("Hollis", "hollischuang");
user.setAddress(new Address("zhejiang", "hangzhou", "binjiang"));

User newUser = new User();
BeanUtils.copyProperties(user, newUser);

System.out.println(user == newUser);
System.out.println(user.getAddress() == newUser.getAddress());
</code></pre>
  <p data-lake-id="uab48e5c1" id="uab48e5c1"><br></p>
  <p data-lake-id="u7825f194" id="u7825f194"><span data-lake-id="u1c3f8a98" id="u1c3f8a98">以上代码输出结果为：</span></p>
  <p data-lake-id="ud72d6dae" id="ud72d6dae"><span data-lake-id="u72a0a0f4" id="u72a0a0f4">​</span><br></p>
  <pre lang="java"><code>
false
true
</code></pre>
  <p data-lake-id="ua7ad508c" id="ua7ad508c"><span data-lake-id="u699d66eb" id="u699d66eb">​</span><br></p>
  <p data-lake-id="u3a47fb95" id="u3a47fb95"><span data-lake-id="ufba0873c" id="ufba0873c">即，我们BeanUtils.copyProperties拷贝出来的newUser是一个新的对象，但是，</span><span data-lake-id="ua0ffb861" id="ua0ffb861">其中的address对象和原来的user中的address对象是同一个对象。</span></p>
  <p data-lake-id="u6c764a96" id="u6c764a96"><span data-lake-id="u618050e6" id="u618050e6">​</span><br></p>
  <p data-lake-id="u791f553e" id="u791f553e"><span data-lake-id="u3db58e7a" id="u3db58e7a">如果我们修改newUser中的Address对象的值的话，是会同时把user对象中的Address的值也修改了的。</span><span data-lake-id="u2742b59e" id="u2742b59e">可以尝试着修改下newUser中的address对象：</span></p>
  <p data-lake-id="uc4eb6b00" id="uc4eb6b00"><span data-lake-id="u426564e6" id="u426564e6">​</span><br></p>
  <pre lang="java"><code>
newUser.getAddress().setCity("shanghai");
System.out.println(JSON.toJSONString(user));
System.out.println(JSON.toJSONString(newUser));
</code></pre>
  <p data-lake-id="u07a97ef8" id="u07a97ef8"><br></p>
  <p data-lake-id="ufbadd576" id="ufbadd576"><span data-lake-id="u7e5933c6" id="u7e5933c6">输出结果：</span></p>
  <p data-lake-id="u9f5a13df" id="u9f5a13df"><span data-lake-id="u419a6d26" id="u419a6d26">​</span><br></p>
  <pre lang="java"><code>
{"address":{"area":"binjiang","city":"shanghai","province":"zhejiang"},"name":"Hollis","password":"hollischuang"}
{"address":{"area":"binjiang","city":"shanghai","province":"zhejiang"},"name":"Hollis","password":"hollischuang"}
</code></pre>
  <h2 data-lake-id="WsnI0" id="WsnI0"><br></h2>
  <h2 data-lake-id="vyeWt" id="vyeWt"><span data-lake-id="u38d03b53" id="u38d03b53">实现深拷贝</span></h2>
  <p data-lake-id="u0bdd5353" id="u0bdd5353"><br></p>
  <p data-lake-id="u278bc3a8" id="u278bc3a8"><span data-lake-id="u47ea4ff7" id="u47ea4ff7">如何实现深拷贝呢，主要有以下几个方法：</span></p>
  <p data-lake-id="u8be48b57" id="u8be48b57"><span data-lake-id="u8b7229fc" id="u8b7229fc">​</span><br></p>
  <h3 data-lake-id="ieD9U" id="ieD9U"><span data-lake-id="u99edcfa9" id="u99edcfa9">实现Cloneable接口，重写clone()</span></h3>
  <p data-lake-id="udb638db0" id="udb638db0"><br></p>
  <p data-lake-id="ufae3af08" id="ufae3af08"><span data-lake-id="u2764dbf5" id="u2764dbf5">在Object类中定义了一个clone方法，这个方法其实在不重写的情况下，其实也是浅拷贝的。</span></p>
  <p data-lake-id="u7f5ca8ac" id="u7f5ca8ac"><span data-lake-id="u76c341b1" id="u76c341b1">如果想要实现深拷贝，就需要重写clone方法，而想要重写clone方法，就必须实现Cloneable，否则会报CloneNotSupportedException异常。</span></p>
  <p data-lake-id="uf4db7d2e" id="uf4db7d2e"><span data-lake-id="uf1bbb303" id="uf1bbb303">​</span><br></p>
  <p data-lake-id="u5ced0b31" id="u5ced0b31"><span data-lake-id="uc3e916ff" id="uc3e916ff">将上述代码修改下，重写clone方法：</span></p>
  <p data-lake-id="u42022e2e" id="u42022e2e"><span data-lake-id="u51ba0f0b" id="u51ba0f0b">​</span><br></p>
  <pre lang="java"><code>
public class Address implements Cloneable{
    private String province;
    private String city;
    private String area;
    //省略构造函数和setter/getter

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class User implements Cloneable{
    private String name;
    private String password;
    private HomeAddress address;
    //省略构造函数和setter/getter

    @Override
    protected Object clone() throws CloneNotSupportedException {
        User user = (User)super.clone();
        user.setAddress((HomeAddress)address.clone());
        return user;
    }
}
</code></pre>
  <p data-lake-id="ub00d7655" id="ub00d7655"><br></p>
  <p data-lake-id="u28714fb2" id="u28714fb2"><span data-lake-id="u895156cf" id="u895156cf">之后，在执行一下上面的测试代码，就可以发现，这时候newUser中的address对象就是一个新的对象了。</span></p>
  <p data-lake-id="uea7d40fa" id="uea7d40fa"><span data-lake-id="ub08f28eb" id="ub08f28eb">​</span><br></p>
  <p data-lake-id="uc600e725" id="uc600e725"><span data-lake-id="ud28f1660" id="ud28f1660">这种方式就能实现深拷贝，但是问题是如果我们在User中有很多个对象，那么clone方法就写的很长，而且如果后面有修改，在User中新增属性，这个地方也要改。</span></p>
  <p data-lake-id="ub34df497" id="ub34df497"><span data-lake-id="u7a84e2a2" id="u7a84e2a2">​</span><br></p>
  <p data-lake-id="ucf79eef5" id="ucf79eef5"><span data-lake-id="ud15503b7" id="ud15503b7">那么，有没有什么办法可以不需要修改，一劳永逸呢？</span></p>
  <p data-lake-id="u8d4a4260" id="u8d4a4260"><span data-lake-id="uf0c506ff" id="uf0c506ff">​</span><br></p>
  <h3 data-lake-id="JztWm" id="JztWm"><span data-lake-id="uff85c22c" id="uff85c22c">序列化实现深拷贝</span></h3>
  <p data-lake-id="uc73d0630" id="uc73d0630"><br></p>
  <p data-lake-id="u4a7924ef" id="u4a7924ef"><span data-lake-id="uea713238" id="uea713238">我们可以借助序列化来实现深拷贝。先把对象序列化成流，再从流中反序列化成对象，这样就一定是新的对象了。</span></p>
  <p data-lake-id="u50562ebb" id="u50562ebb"><span data-lake-id="u21f6738c" id="u21f6738c">序列化的方式有很多，比如我们可以使用各种JSON工具，把对象序列化成JSON字符串，然后再从字符串中反序列化成对象。</span></p>
  <p data-lake-id="u660a99c2" id="u660a99c2"><span data-lake-id="ud1a1e9e0" id="ud1a1e9e0">​</span><br></p>
  <p data-lake-id="u79efc9c0" id="u79efc9c0"><span data-lake-id="ud2f7eeda" id="ud2f7eeda">如使用fastjson实现：</span></p>
  <p data-lake-id="u3c0a8654" id="u3c0a8654"><span data-lake-id="u2379e8fc" id="u2379e8fc">​</span><br></p>
  <pre lang="java"><code>
User newUser = JSON.parseObject(JSON.toJSONString(user), User.class);
</code></pre>
  <p data-lake-id="u40fa626f" id="u40fa626f"><br></p>
  <p data-lake-id="u257a3b52" id="u257a3b52"><span data-lake-id="u7499725e" id="u7499725e">也可实现深拷贝。</span></p>
  <p data-lake-id="u874aea76" id="u874aea76"><span data-lake-id="u2231cefc" id="u2231cefc">​</span><br></p>
  <p data-lake-id="udc9d8204" id="udc9d8204"><span data-lake-id="u68850deb" id="u68850deb">除此之外，还可以使用Apache Commons Lang中提供的SerializationUtils工具实现。</span></p>
  <p data-lake-id="u33460001" id="u33460001"><span data-lake-id="u95fc87e4" id="u95fc87e4">​</span><br></p>
  <p data-lake-id="u6f08de6d" id="u6f08de6d"><span data-lake-id="u4fde4834" id="u4fde4834">我们需要修改下上面的User和Address类，使他们实现Serializable接口，否则是无法进行序列化的。</span></p>
  <p data-lake-id="u2114527b" id="u2114527b"><span data-lake-id="u1f33f5d6" id="u1f33f5d6">​</span><br></p>
  <pre lang="java"><code>
class User implements Serializable
class Address implements Serializable
</code></pre>
  <p data-lake-id="u9629b575" id="u9629b575"><br></p>
  <p data-lake-id="ufaafe5d1" id="ufaafe5d1"><span data-lake-id="ufd1775cf" id="ufd1775cf">然后在需要拷贝的时候：</span></p>
  <pre lang="java"><code>
User newUser = (User) SerializationUtils.clone(user);
</code></pre>
  <p data-lake-id="ue4c5d424" id="ue4c5d424"><span data-lake-id="u3c889d66" id="u3c889d66">​</span><br></p>
  <p data-lake-id="uf187753c" id="uf187753c"><span data-lake-id="u42903c08" id="u42903c08">同样，也可以实现深拷贝啦~！</span></p>
 </body>
</html>